package org.zoomdev.zoom.dao.entity;

import org.apache.commons.lang3.StringUtils;
import org.zoomdev.zoom.common.exceptions.ZoomException;
import org.zoomdev.zoom.common.utils.CachedClasses;
import org.zoomdev.zoom.common.utils.Classes;
import org.zoomdev.zoom.dao.Dao;
import org.zoomdev.zoom.dao.DaoException;
import org.zoomdev.zoom.dao.DatabaseConfig;
import org.zoomdev.zoom.dao.DatabaseStruct;
import org.zoomdev.zoom.dao.Entity;
import org.zoomdev.zoom.dao.EntityBuilder;
import org.zoomdev.zoom.dao.EntityField;
import org.zoomdev.zoom.dao.RenameStrategy;
import org.zoomdev.zoom.dao.adapters.DataAdapter;
import org.zoomdev.zoom.dao.adapters.ResultSetAdapter;
import org.zoomdev.zoom.dao.adapters.StatementAdapter;
import org.zoomdev.zoom.dao.adapters.result.ResultSetAdapters;
import org.zoomdev.zoom.dao.adapters.statement.StatementAdapters;
import org.zoomdev.zoom.dao.adapters.statement.WrappedStatementAdapter;
import org.zoomdev.zoom.dao.annotations.AutoGenerate;
import org.zoomdev.zoom.dao.annotations.Column;
import org.zoomdev.zoom.dao.annotations.ColumnIgnore;
import org.zoomdev.zoom.dao.annotations.PrimaryKey;
import org.zoomdev.zoom.dao.annotations.Table;
import org.zoomdev.zoom.dao.impl.BeanEntity;
import org.zoomdev.zoom.dao.impl.RecordEntity;
import org.zoomdev.zoom.dao.structs.ColumnMeta;
import org.zoomdev.zoom.dao.structs.TableMeta;
import org.zoomdev.zoom.dao.utils.DaoUtils;
import org.zoomdev.zoom.dao.utils.SqlUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;

public class EntityBuilderImpl implements EntityBuilder {

    private final DatabaseStruct struct;
    private final List<EntityField> fields = new ArrayList<>();

    private final List<EntityField> autoGenerateFields = new ArrayList<>();

    private final DatabaseConfig config;
    private final Map<String, TableInfo> tableInfoMap = new HashMap<>();
    private final List<String[]> binds = new ArrayList<>();
    private final Dao dao;
    private Class<?> type;
    private String[] table;
    private String tableName;
    private boolean isMap = false;

    public EntityBuilderImpl(Dao dao, DatabaseStruct struct, DatabaseConfig config) {
        this.struct = struct;
        this.config = config;
        this.dao = dao;
    }

    @Override
    public EntityBuilder setType(Class<?> type) {
        this.type = type;
        return this;
    }

    @Override
    public EntityBuilder setTable(String... table) {
        this.table = table;
        return this;
    }

    @Override
    public EntityBuilder bind(String field, String column) {
        binds.add(new String[]{field, column});
        return this;
    }

    public TableInfo getTableInfo(String table) {
        return tableInfoMap.computeIfAbsent(table.toLowerCase(), k -> {
            TableMeta defaultTableMeta = struct.getTableMeta(table);
            RenameStrategy strategy = this.config.getColumnRenameStrategy().getStrategy(defaultTableMeta.getColumnNames());
            Map<String, ColumnMeta> columnMetaMap = new HashMap<>();
            for (ColumnMeta columnMeta : defaultTableMeta.getColumns()) {
                columnMetaMap.put(strategy.getFieldName(columnMeta.getName()), columnMeta);
            }
            TableInfo info = new TableInfo(defaultTableMeta, columnMetaMap);
            return info;
        });

    }

    @Override
    public Entity build() {
        if (table == null || table.length == 0) {
            if (type == null) {
                throw new ZoomException("必须至少指定一个table或者type才能构建Entity");
            }

            if (Map.class.isAssignableFrom(type)) {
                throw new ZoomException("Map类型定义的ActiveRecord必须定义table");
            } else {
                Table table = type.getAnnotation(Table.class);
                if (StringUtils.isBlank(table.value())) {
                    throw new ZoomException("@Table标注的value属性不能为空");
                }
                this.tableName = table.value().trim();
            }
        } else {
            //默认第一张表
            this.tableName = table[0];
        }
        isMap = type != null && Map.class.isAssignableFrom(type);
        if (!isMap && type != null) {
            if (type.isInterface()) {
                throw new ZoomException("不支持接口" + type);
            }
        }

        TableInfo tableInfo = getTableInfo(tableName);
        if (isMap) {
            return buildMap(tableInfo);
        }
        return buildBean(tableInfo);
    }

    private Entity buildMap(TableInfo tableInfo) {
        List<EntityField> entityFields = new ArrayList<>(tableInfo.columnMetaMap.size());
        for (Map.Entry<String, ColumnMeta> entry : tableInfo.columnMetaMap.entrySet()) {
            ColumnMeta meta = entry.getValue();
            String field = entry.getKey();
            StatementAdapter statementAdapter = StatementAdapters.create(meta.getColumnType());
            Class fieldType = DaoUtils.normalizeType(meta.getColumnType());
            ResultSetAdapter resultSetAdapter = ResultSetAdapters.create(meta.getColumnType(), fieldType);
            RecordEntityField recordEntityField = new RecordEntityField(
            );
            entityFields.add(recordEntityField);
        }

        List<EntityField> primaryKeys = new ArrayList<>();
        parsePrimaryKeys(primaryKeys, entityFields);


        return new RecordEntity(dao.sqlDriver(),
                tableInfo.tableMeta,
                primaryKeys.toArray(new EntityField[primaryKeys.size()]),
                entityFields.toArray(new EntityField[entityFields.size()]));
    }

    private Entity buildBean(TableInfo tableInfo) {
        Field[] fields = CachedClasses.getFields(type);
        List<EntityField> entityFields = new ArrayList<>();
        for (Field field : fields) {
            // 内部类
            if (field.getName().startsWith("this$")) {
                continue;
            }
            if (field.isAnnotationPresent(ColumnIgnore.class)) {
                continue;
            }

            Type[] types = null;
            DataAdapter dataAdapter = null;
            Column column = field.getAnnotation(Column.class);
            if (column != null && column.adapter() != DataAdapter.class) {
                types = Classes.getAllParameterizedTypes(column.adapter());
                if (types == null) {
                    throw new DaoException(column.adapter() + "必须是泛型类型");
                }
                if (types.length < 2) {
                    throw new DaoException(column.adapter() + "必须有至少两个泛型类型");
                }
                dataAdapter = createDataAdapter(column.adapter());
            }

            ColumnMeta columnMeta = getColumnMeta(column, field, tableInfo);
            if (columnMeta == null) {
                throw new ZoomException("找不到字段" + field + "对应的数据库字段");
            }
            StatementAdapter statementAdapter = createStatementAdapter(types, dataAdapter, field, columnMeta);
            ResultSetAdapter resultSetAdapter = createResultSetAdapter(types, dataAdapter, field, columnMeta);
            EntityField entityField = new BeanEntityField();
            entityFields.add(entityField);
        }

        List<EntityField> primaryKeys = new ArrayList<>();
        handlePrimaryKey(primaryKeys, entityFields);


        return new BeanEntity(type,
                dao,
                tableInfo.tableMeta,
                primaryKeys.toArray(new EntityField[primaryKeys.size()]),
                entityFields.toArray(new EntityField[entityFields.size()]));
    }

    private ResultSetAdapter createResultSetAdapter(Type[] dataAdapterTypes,
                                                    DataAdapter dataAdapter,
                                                    Field field,
                                                    ColumnMeta columnMeta) {
        if (dataAdapter == null) {
            ResultSetAdapter adapter = ResultSetAdapters.create(columnMeta.getColumnType(), field.getGenericType());
            return adapter;
        }


        return null;
    }

    private DataAdapter createDataAdapter(Class<?> type) {
        return (DataAdapter) Classes.newInstance(type);
    }

    private StatementAdapter createStatementAdapter(Type[] dataAdapterTypes,
                                                    DataAdapter dataAdapter,
                                                    Field field,
                                                    ColumnMeta columnMeta) {
        StatementAdapter adapter = StatementAdapters.create(field.getType(), columnMeta.getColumnType());
        if (dataAdapter != null) {
            return new WrappedStatementAdapter(dataAdapter, adapter);
        }
        return adapter;
    }


    private ColumnMeta getColumnMeta(Column column, Field field, TableInfo tableInfo) {

        if (column != null) {
            if (!StringUtils.isBlank(column.value())) {
                String value = column.value();
                Matcher matcher = SqlUtils.TABLE_AND_COLUMN_PATTERN.matcher(value);
                if (matcher.matches()) {
                    String tableName = matcher.group(1);
                    String columnName = matcher.group(2);
                    //其他表的字段
                    TableInfo other = getTableInfo(tableName);
                    return other.getColumnMetaByColumnName(columnName);
                } else {
                    throw new DaoException("在field:" + field + " 标注@Column(value=" + value + ")格式不正确，应该为 table.column 或者 column,不区分大小写");
                }

            }
        }

        return tableInfo.getColumnMetaByFieldName(field.getName());
    }


    private void handlePrimaryKey(List<EntityField> primaryKeys, List<EntityField> entityFields) {
        for (EntityField entityField : entityFields) {
            BeanEntityField beanEntityField = (BeanEntityField) entityField;
            PrimaryKey primaryKey = beanEntityField.getField().getAnnotation(PrimaryKey.class);
            if (primaryKey != null) {
                primaryKeys.add(entityField);
            }
        }

        parsePrimaryKeys(primaryKeys, entityFields);
    }


    private void parsePrimaryKeys(List<EntityField> primaryKeys, List<EntityField> entityFields) {
        if (primaryKeys.size() == 0) {
            for (EntityField entityField : entityFields) {
                if (entityField.getColumn().getKeyType() == ColumnMeta.KeyType.PRIMARY) {
                    primaryKeys.add(entityField);
                }
            }
        }
    }

    private void handleAutoGenerate(Field field, EntityField entityField) {

        //处理自动生成键
        AutoGenerate autoGenerate = field.getAnnotation(AutoGenerate.class);
        if (autoGenerate != null) {
            autoGenerateFields.add(entityField);
        }
    }

    class TableInfo {
        final TableMeta tableMeta;
        final Map<String, ColumnMeta> columnMetaMap;

        TableInfo(TableMeta tableMeta, Map<String, ColumnMeta> columnMetaMap) {
            this.tableMeta = tableMeta;
            this.columnMetaMap = columnMetaMap;
        }


        public ColumnMeta getColumnMetaByFieldName(String field) {
            return columnMetaMap.get(field);
        }

        public ColumnMeta getColumnMetaByColumnName(String columnName) {
            return tableMeta.getColumn(columnName);
        }
    }


}
